const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
/**
 * Promise A+规范
 * 三种状态：进行中、已成功、已拒绝
 * 一旦状态发生变化就固定了
 */
class MyPromise {
  #state = PENDING;
  #result = undefined;
  #handlers = [];

  /**
   * 构造函数
   * @param {Function} executor 执行器
   */
  constructor(executor) {
    const resolve = data => {
      this.#changeState(FULFILLED, data);
    };

    const reject = reason => {
      this.#changeState(REJECTED, reason);
    };

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  /**
   * 改变状态函数
   * @param {string} state 状态
   * @param {any} result 结果
   * @returns
   */
  #changeState(state, result) {
    if (this.#state !== PENDING) return;
    this.#state = state;
    this.#result = result;
    this.#run();
  }

  /**
   * 放进微任务队列中
   * @param {Function} func 回调函数
   */
  #runMicrotask(func) {
    if (typeof process === "object" && typeof process.nextTick === "function") {
      process.nextTick(func);
    } else if (typeof MutationObserver === "function") {
      const ob = new MutationObserver();
      const textNode = document.createTextNode("1");
      ob.observe(textNode, { characterData: true });
      textNode.data = "2";
    } else {
      setTimeout(func, 0);
    }
  }

  /**
   * 封装执行函数
   * @param {Function} callback
   * @param {Function} resolve
   * @param {Function} reject
   */
  #runOne(callback, resolve, reject) {
    this.#runMicrotask(() => {
      if (typeof callback !== "function") {
        const settled = this.#state === FULFILLED ? resolve : reject;
        settled(this.#result);
        return;
      }
      try {
        const data = callback(this.#result);
        if (this.#isPromiseLike(data)) {
          data.then(resolve, reject);
        } else {
          resolve(data);
        }
      } catch (error) {
        reject(error);
      }
    });
  }

  /**
   * 执行回调
   * @returns void
   */
  #run() {
    if (this.#state === PENDING) return;
    while (this.#handlers.length) {
      const { onFulfilled, onRejected, resolve, reject } = this.#handlers.shift();
      if (this.#state === FULFILLED) {
        this.#runOne(onFulfilled, resolve, reject);
      } else {
        this.#runOne(onRejected, resolve, reject);
      }
    }
  }

  /**
   * 判断是否是Promise
   * @param {any} value
   * @returns
   */
  #isPromiseLike(value) {
    if (value !== null && (typeof value === "object" || typeof value === "function")) {
      return typeof value.then === "function";
    }
    return false;
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.#handlers.push({ onFulfilled, onRejected, resolve, reject });
      this.#run();
    });
  }

  catch(onRejected) {
    return this.then(undefined, onRejected);
  }

  static resolve(value) {
    if (value instanceof MyPromise) return value;
    let _resolve, _reject;
    const p = new MyPromise((resolve, reject) => {
      _resolve = resolve;
      _reject = reject;
    });
    if (p.#isPromiseLike(value)) {
      value.then(_resolve, _reject);
    } else {
      _resolve(value);
    }

    return p;
  }

  static reject(reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason);
    });
  }

  /* static all(proms) {
    const result = [];
    let _resolve, _reject;
    let count = 0,
      finishNum = 0;

    const p = new MyPromise((resolve, reject) => {
      _resolve = resolve;
      _reject = reject;
    });

    for (const prom of proms) {
      const index = count;
      count++;
      MyPromise.resolve(prom).then(data => {
        result[index] = data;
        finishNum++;
        if (finishNum === count) {
          _resolve(result);
        }
      }, _reject);
    }

    if (count === 0) {
      _resolve([]);
    }

    return p;
  } */

  static async all(proms) {
    const results = [];
    for (const prom of proms) {
      results.push(await prom);
    }

    return results;
  }
}

// 测试
const p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(10);
  }, 1000);
});

const p2 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(20);
  }, 2000);
});

const p3 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    reject("error");
  }, 3000);
});

MyPromise.all([2, 3, Promise.reject("e")])
  .then(results => {
    console.log(results);
  })
  .catch(error => {
    console.log(error);
  });
